#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRep_Builder.hxx>
#include <BRepLib_MakeEdge.hxx>

#include "Qt3DPicker.h"
#include "OsgUtil.h"
#include "PolygonWithHolesHandler.h"
#include "CmdManager.h"
#include "Scene.h"
#include "SendPointHandlerBase.h"
#include "Operation/OpAdd.h"
#include "Polygon.h"
#include "Surface.h"
#include "PointHandler.h"
#include "SelectHandler3D.h"
#include "OSGNodeMask.h"
#include "LineStrip3D.h"
#include "Plane.h"
#include "LineStrip2D.h"
#include "QtOSGWidget.h"
#include "OcctMisc.h"
#include "OcctOsg.h"

bool getConvexHull(const std::vector<osg::Vec3d> &points, std::vector<osg::Vec3d>& hullPoints);
void removeCollinearPoint(std::vector<osg::Vec3d>& points, double detError = 0.01);

PolygonWithHolesHandler::PolygonWithHolesHandler()
{
}

PolygonWithHolesHandler::~PolygonWithHolesHandler()
{
}

void getPoints(const std::shared_ptr<INode>& ent, std::vector<osg::Vec3d>& points)
{
	if (ent->metaObject() == &LineStrip2D::staticMetaObject)
	{
		std::shared_ptr<LineStrip2D> line = std::static_pointer_cast<LineStrip2D>(ent);
		auto points2D = line->getPoints();
		auto matrix = line->getRep3D()->getWorldMatrices()[0]/* * VPW*/;
		for (auto pt : points2D)
		{
			points.push_back(pt * matrix);
		}
	}
	else if (ent->metaObject() == &LineStrip3D::staticMetaObject)
	{
		std::shared_ptr<LineStrip3D> line = std::static_pointer_cast<LineStrip3D>(ent);
		points = line->getPoints();
	}
}

bool getLineLoopPoints(const std::set<std::shared_ptr<INode>>& lines, std::vector<osg::Vec3d>& points)
{
	std::vector<std::vector<osg::Vec3d>> pointsVec;
	for (const auto& ent : lines)
	{
		std::vector<osg::Vec3d> thisPoints;
		getPoints(ent, thisPoints);
		if (thisPoints.size() >= 2)
			pointsVec.push_back(thisPoints);
	}

	if (pointsVec.empty())
		return false;
	points = pointsVec.back();
	pointsVec.pop_back();
	while(!pointsVec.empty())
	{
		int count = pointsVec.size();
		for (auto it = pointsVec.begin(); it != pointsVec.end(); ++it)
		{
			const auto& thisPoints = *it;
			if (pointApprox(thisPoints.front(), points.back()))
			{
				for (int i = 1; i < thisPoints.size(); ++i)
					points.push_back(thisPoints[i]);
				pointsVec.erase(it);
				break;
			}
			else if (pointApprox(thisPoints.back(), points.back()))
			{
				for (int i = thisPoints.size() - 2; i >= 0; --i)
					points.push_back(thisPoints[i]);
				pointsVec.erase(it);
				break;
			}
			else if (pointApprox(thisPoints.front(), points.front()))
			{
				for (int i = 1; i < thisPoints.size(); ++i)
					points.insert(points.begin(), thisPoints[i]);
				pointsVec.erase(it);
				break;
			}
			else if (pointApprox(thisPoints.back(), points.front()))
			{
				for (int i = thisPoints.size() - 2; i >= 0; --i)
					points.insert(points.begin(), thisPoints[i]);
				pointsVec.erase(it);
				break;
			}
		}
		int count2 = pointsVec.size();
		if (count2 == count)
			break;
	}

	if (!pointsVec.empty())
		return false;

	if (pointApprox(points.back(), points.front()))
		points.pop_back();
	
	return (points.size() >= 3);
}

void PolygonWithHolesHandler::input(const QVariant& data, const QString& rawInput, CommandSource cs)
{
	auto sph = getSendPointHandler();
	auto scene = getScene();
	auto widget3D = getWidget();
	auto selector = qobject_cast<SelectHandler3D*>(getSelectHandler());
	if (data.type() == QVariant::Bool && !data.toBool())
	{
		selector->clearSelection();
		emit finished();
		return;
	}

	if (status() == 0)
	{
		if (selector)
		{
			selector->clearSelection();
			selector->beginSelect(D2LineMask | D3LineMask);
			prompt(1, tr("选择外环直线: "));
		}
		return;
	}

	if (status() == 1)
	{
		if (selector && selector->done())
		{
			auto selections = selector->selectionSet();
			if (selections.empty())
			{
				remark(tr("没有选中对象，取消命令"));
				selector->clearSelection();
				emit finished();
				return;
			}
			bool success = getLineLoopPoints(selections, m_outerloop);
			if (success)
			{
				selector->clearSelection();
				selector->beginSelect(D2LineMask | D3LineMask);
				prompt(2, tr("选择内环直线或者<结束命令>: "));
			}
			else
			{
				remark(tr("不能形成闭环，取消命令"));
				selector->clearSelection();
				emit finished();
				return;
			}
		}
		return;
	}


	if (status() == 2)
	{
		if (selector && selector->done())
		{
			auto selections = selector->selectionSet();
			if (selections.empty())
			{
				doIt();
				emit finished();
				return;
			}
			std::vector<osg::Vec3d> innerLoop;
			bool success = getLineLoopPoints(selections, innerLoop);
			if (success)
			{
				m_innerloops.push_back(innerLoop);
			}
			selector->clearSelection();
			selector->beginSelect(D2LineMask | D3LineMask);
			prompt(2, tr("选择内环直线或者<结束命令>: "));
		}
		return;
	}
}

osg::Vec3d getOrigin(const osg::Vec3d& normal, double d)
{
	osg::Vec3d origin;
	int index = -1;
	double maxCom = 0;
	if (std::abs(normal.x()) > std::abs(normal.y()))
	{
		maxCom = std::abs(normal.x());
		index = 0;
	}
	else
	{
		maxCom = std::abs(normal.y());
		index = 1;
	}
	if (maxCom < std::abs(normal.z()))
	{
		index = 2;
	}
	switch (index)
	{
	case 0:
		origin.x() = -d / normal.x();
		break;
	case 1:
		origin.y() = -d / normal.y();
		break;
	case 2:
		origin.z() = -d / normal.z();
		break;
	}
	return origin;
}

bool makePolygon(Scene* scene, const std::vector<osg::Vec3d>& points, std::shared_ptr<Polygon>& polygon, QString& errorString)
{
	//判断是否共面
	osg::Vec3d normal;
	double d;
	bool success = getPlaneParameter(points, normal, d);
	if (!success)
	{
		errorString = "存在三点共线";
		return false;
	}
	//  计算origin
	osg::Vec3d origin = getOrigin(normal, d);

	Plane  plane;
	plane.setOrigin(origin);
	if (pointApprox(points[1], points[0]))
	{
		errorString = "第一点和第二点重合";
		return false;
	}
	success = plane.setAxis2(points[1] - points[0], normal);

	std::vector<osg::Vec2d> planePoints;
	auto m = plane.getMatrix();
	m = osg::Matrixd::inverse(m);
	for (const auto& pt : points)
	{
		auto planePt = pt * m;
		planePoints.push_back(osg::Vec2d(planePt.x(), planePt.y()));
	}
	polygon = std::make_shared<Polygon>(scene);
	polygon->setOrigin(plane.origin());
	polygon->setAxis(plane.xAxis(), plane.yAxis());
	polygon->setPoints(planePoints);
	return true;
}

TopoDS_Wire makeWire(const std::vector<osg::Vec3d>& points)
{
	const double VERTEX_TOLERANCE = 1E-4;
	BRepBuilderAPI_MakeWire wireMaker;
	std::vector<TopoDS_Vertex> vertice;
	for (int i = 0; i < points.size(); ++i)
	{
		TopoDS_Vertex vertex;
		BRep_Builder builder;
		builder.MakeVertex(vertex, toPnt(points[i]), VERTEX_TOLERANCE);
		vertice.push_back(vertex);
	}
	for (int i = 0; i < points.size(); ++i)
	{
		BRepLib_MakeEdge maker(vertice[i], vertice[(i + 1) % points.size()]);
		if (maker.IsDone())
		{
			TopoDS_Edge edge = maker.Edge();
			wireMaker.Add(edge);
		}
	}
	if (wireMaker.IsDone())
	{
		return wireMaker.Wire();
	}
	return TopoDS_Wire();
}

void PolygonWithHolesHandler::doIt()
{
	if (m_outerloop.size() < 3)
		return;
	auto scene = getScene();
	if (m_innerloops.empty())
	{
		QString errorString;
		std::shared_ptr<Polygon> polygon;
		bool success = makePolygon(scene, m_outerloop, polygon, errorString);
		if (success)
		{
			auto undoStack = scene->undoStack();
			std::set<std::shared_ptr<INode>> children;
			children.insert(polygon);
			undoStack->push(new OpAdd(children));
		}
		else
		{
			remark(errorString);
		}
	}
	else
	{
		//判断是否共面
		osg::Vec3d outerLoopNormal;
		double outLoopD;
		bool success = getPlaneParameter(m_outerloop, outerLoopNormal, outLoopD);
		if (!success)
		{
			remark(tr("外环存在三点共线"));
			return;
		}
		std::vector<osg::Vec3d> hull;
		success = getConvexHull(m_outerloop, hull);
		if (success)
		{
			removeCollinearPoint(hull, 0.01);
			if (hull.size() >= 3)
			{
				auto normal2 = (hull[1] - hull[0]) ^ (hull[2] - hull[1]);
				normal2.normalize();
				double dot = outerLoopNormal * normal2;
				if (dot < 0)
				{
					outerLoopNormal = -outerLoopNormal;
					outLoopD = -outLoopD;
				}
			}
		}
		//  计算origin
		osg::Vec3d outerLoopPlaneOrigin = getOrigin(outerLoopNormal, outLoopD);

		// 检查内环点是否在外环平面上
		for (int i = 0; i < m_innerloops.size(); ++i)
		{
			auto& innerLoop = m_innerloops[i];
			osg::Vec3d innerLoopNormal;
			double innerLoopD;
			bool success = getPlaneParameter(innerLoop, innerLoopNormal, innerLoopD);
			if (!success)
			{
				remark(tr("第%1个内环存在三点共线").arg(i));
				return;
			}
			std::vector<osg::Vec3d> hull;
			success = getConvexHull(innerLoop, hull);
			if (success)
			{
				removeCollinearPoint(hull, 0.01);
				if (hull.size() >= 3)
				{
					auto normal3 = (hull[1] - hull[0]) ^ (hull[2] - hull[1]);
					normal3.normalize();
					double dot = innerLoopNormal * normal3;
					if (dot < 0)
					{
						innerLoopNormal = -innerLoopNormal;
						innerLoopD = -innerLoopD;
					}
				}
			}

			double dot = outerLoopNormal * innerLoopNormal;
			if (dot < 0)
			{
				innerLoopNormal = -innerLoopNormal;
				innerLoopD = -innerLoopD;
				dot = -dot;
			}
			else
			{
				// 确保内环方向和外环相反
				std::reverse(innerLoop.begin(), innerLoop.end());
			}
			//  计算origin
			osg::Vec3d innerLoopPlaneOrigin = getOrigin(innerLoopNormal, innerLoopD);
			if (dot < 0.9999984769132877/*cos(0.1deg)*/)
			{
				remark(tr("第%1个内环与外环法线偏差超过0.1度").arg(i));
				return;
			}
			if (!pointApprox(outerLoopPlaneOrigin, innerLoopPlaneOrigin))
			{
				remark(tr("第%1个内环与外环平面不重合").arg(i));
				return;
			}
		}

		TopoDS_Wire wire = makeWire(m_outerloop);
		BRepBuilderAPI_MakeFace aFaceMaker(wire, true);
		for (int i = 0; i < m_innerloops.size(); ++i)
		{
			const auto& innerLoop = m_innerloops[i];
			TopoDS_Wire wire2 = makeWire(innerLoop);
			aFaceMaker.Add(wire2);
		}

		if (aFaceMaker.IsDone())
		{
			auto face = aFaceMaker.Face();
			auto surface = std::make_shared<Surface>(scene);
			surface->setShape(face);
			auto undoStack = scene->undoStack();
			std::set<std::shared_ptr<INode>> children;
			children.insert(surface);
			undoStack->push(new OpAdd(children));
		}
	}
}

////////////////////class seperator//////////////////////////////

PolygonWithHoles2Handler::PolygonWithHoles2Handler()
{
}

PolygonWithHoles2Handler::~PolygonWithHoles2Handler()
{
}

void PolygonWithHoles2Handler::input(const QVariant& data, const QString& rawInput, CommandSource cs)
{
	auto scene = getScene();
	auto widget3D = getWidget();
	auto sph = getSendPointHandler();
	if (data.type() == QVariant::Bool)
	{
		bool esc = !data.toBool();
		if (esc)
		{
			if (m_line)
			{
				widget3D->pauseRenderThread();
				scene->removeChild(m_line);
				widget3D->resumeRenderThread();
			}
			clearInput();
			emit finished();
			return;
		}
	}

	if (status() == 0)
	{
		if (sph)
		{
			sph->setPointMask(CS_LBTNDOWN);
		}
		m_line = std::make_shared<LineStrip3D>(scene);
		m_line->setColor(getCurrentColor());
		widget3D->pauseRenderThread();
		scene->addChild(m_line);
		widget3D->resumeRenderThread();

		m_pointStateMachine = std::make_shared<PointHandler>();
		m_pointStateMachine->setCmdManager(cmdManager());
		m_pointStateMachine->setPrompt0(tr("指定外环第一点<结束命令>"));
		m_pointStateMachine->input(data, rawInput, cs);
		setStatus(1);
		return;
	}

	std::vector<osg::Vec3d> points = m_line->getPoints();

	if (status() >= 1)
	{
		if (data.type() == QVariant::Bool && data.toBool())
		{
			if (points.empty())
			{
				doIt();
				emit finished();
				return;
			}
			points.pop_back();
			if (!points.empty())
			{
				if (pointApprox(points.front(), points.back()))
					points.pop_back();
			}
			if (points.size() < 3)
			{
				remark(tr("不够三点"));
				emit finished();
				return;
			}
			if (m_outerloop.empty())
				m_outerloop = points;
			else
				m_innerloops.push_back(points);

			widget3D->pauseRenderThread();
			points.clear();
			m_line->setPoints(points);
			widget3D->resumeRenderThread();
			if (sph)
			{
				sph->setPointMask(CS_LBTNDOWN);
			}
			setStatus(status() + 1);
			m_pointStateMachine->setPrompt0(tr("指定内环第一点<结束命令>"));
			m_pointStateMachine->reset();
			return;
		}

		if (cs == CS_MOUSEMOVE)
		{
			if (data.type() == QVariant::Vector3D)
			{
				if (!points.empty())
				{
					auto pt = data.value<QVector3D>();
					osg::Vec3d worldPt = { pt.x(), pt.y(), pt.z() };
					points[points.size() - 1] = worldPt;
				}
			}
			m_line->setPoints(points);
		}
		else
		{
			m_pointStateMachine->input(data, rawInput, cs);
			if (m_pointStateMachine->done())
			{
				osg::Vec3d worldPt = m_pointStateMachine->getPoint();
				if (points.empty())
				{
					points.push_back(worldPt);
					if (sph)
					{
						sph->setPointMask(CS_LBTNDOWN | CS_MOUSEMOVE);
					}
				}
				else
				{
					points[points.size() - 1] = worldPt;
				}
				points.push_back(worldPt);
				widget3D->pauseRenderThread();
				m_line->setPoints(points);
				widget3D->resumeRenderThread();
				setStatus(status() + 1);
				if (m_outerloop.empty())
					m_pointStateMachine->setPrompt0(tr("指定外环下一点<结束外环>"));
				else
					m_pointStateMachine->setPrompt0(tr("指定内环下一点<结束内环>"));
				m_pointStateMachine->reset();
			}
		}
		return;
	}
}
